import datetime

from django.db import transaction
from django.db.models import F, When, Case, Sum
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer

from finance.api.utils import update_account
from purchase.api.utils import increase_entry_stats
from purchase.models import WareHouseEntry, WareHouseEntryDetail, PurchasePayOrder, PurchasePayOrderDetail
from sale.models import SaleOrderDetail
from store.api.utils import decrease_goods_stock, increase_goods_stock, update_stock_price
from store.models import GoodsStock


class WareHouseEntryDetailSerializer(ModelSerializer):
    """
    入库单明细
    """
    id = serializers.IntegerField(required=False)
    goods_code = serializers.CharField(source='goods.code', read_only=True)
    goods_name = serializers.CharField(source='goods.name', read_only=True)
    goods_cate_name = serializers.CharField(source='goods.cate.name', read_only=True)
    goods_specs = serializers.CharField(source='goods.specs', read_only=True)
    goods_unit_name = serializers.CharField(source='goods.unit.name', read_only=True)
    entry_code = serializers.CharField(source='entry.code', read_only=True)
    entry_time = serializers.CharField(source='entry.warehouse_time', read_only=True)
    entry_house = serializers.CharField(source='entry.house.name', read_only=True)
    supplier_name = serializers.CharField(source='entry.supplier.name', read_only=True)
    sale_order_code = serializers.CharField(source='sale_detail.sale_order.code', read_only=True)
    lend_nums = serializers.CharField(source='sale_detail.nums', read_only=True)
    return_nums = serializers.CharField(source='sale_detail.return_nums', read_only=True)

    class Meta:
        model = WareHouseEntryDetail
        exclude = ["update_time", "add_time"]


class WareHouseEntrySerializer(ModelSerializer):
    """
    商品入库单
    """
    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
    buyer_name = serializers.CharField(source='buyer.name', read_only=True)
    house_name = serializers.CharField(source='house.name', read_only=True)
    status_name = serializers.CharField(source='get_status_display', read_only=True)
    flag_name = serializers.CharField(source='get_flag_display', read_only=True)
    account_name = serializers.CharField(source='account.name', read_only=True)
    oper_user_name = serializers.CharField(source='oper_user.real_name', read_only=True)
    nopay_amount = serializers.SerializerMethodField(read_only=True)
    lists = WareHouseEntryDetailSerializer(many=True, required=False)

    class Meta:
        model = WareHouseEntry
        exclude = ["add_time"]

    def get_nopay_amount(self, obj):
        total_amount = obj.total_amount or 0
        amount = obj.amount or 0
        return total_amount - amount

    def create(self, validated_data):
        lists = validated_data.pop('lists')
        with transaction.atomic():
            # 创建事务保存点
            save_id = transaction.savepoint()
            instance = None
            try:
                flag = validated_data.get('flag', 0)
                imprest = validated_data.get('imprest', False)
                if flag == 1:   # 退款
                    validated_data["total_amount"] *= -1
                    validated_data["amount"] *= -1
                    if imprest:
                        validated_data["imp_amount"] *= -1
                    for item in lists:
                        if item.get("nums") > 0:
                            item["nums"] = -1 * item.get("nums")
                        if item.get("total_amount") > 0:
                            item["total_amount"] = -1 * item.get("total_amount")

                instance = self.Meta.model.objects.create(**validated_data)

                # 直接付款
                if imprest:
                    account = validated_data.get("account")
                    imp_amount = validated_data.get("imp_amount")
                    update_account(account.id, imp_amount)

                for item in lists:

                    item["batch_num"] = None if not item.get("batch_num", None) or item["batch_num"].strip() == "" else item["batch_num"]

                create_list = [WareHouseEntryDetail(entry=instance, **l) for l in lists]
                objs = WareHouseEntryDetail.objects.bulk_create(create_list)
                for obj in objs:
                    increase_entry_stats(obj)   # 计算进货量
                    self.update_goods_stock(instance.flag, obj)
                    if flag == 3:   # 还回登记
                        self.update_return_nums(obj)

            except Exception as e:
                print(e)
                transaction.savepoint_rollback(save_id)

            transaction.savepoint_commit(save_id)
        return instance

    def update(self, instance, validated_data):
        lists = validated_data.pop('lists')
        with transaction.atomic():
            # 创建事务保存点
            save_id = transaction.savepoint()
            try:
                instance.code = validated_data.get('code', instance.code)
                instance.warehouse_time = validated_data.get('warehouse_time', instance.warehouse_time)
                instance.supplier = validated_data.get('supplier', instance.supplier)
                instance.buyer = validated_data.get('buyer', instance.buyer)

                old_house_id = instance.house.id
                instance.house = validated_data.get('house', instance.house)

                instance.flag = validated_data.get('flag', instance.flag)
                instance.total_amount = validated_data.get('total_amount', instance.total_amount)
                instance.amount = validated_data.get('amount', instance.amount)
                instance.status = validated_data.get('status', instance.status)

                old_imprest = instance.imprest
                imprest = validated_data.get('imprest', False)

                old_account = instance.account
                account = validated_data.get('account', None)

                old_imp_amount = instance.imp_amount
                imp_amount = validated_data.get('imp_amount', 0)

                instance.imprest = validated_data.get('imprest', instance.imprest)
                instance.account = validated_data.get('account', instance.account)
                instance.imp_amount = validated_data.get('imp_amount', instance.imp_amount)
                instance.remark = validated_data.get('remark', instance.remark)
                instance.oper_user = validated_data.get('oper_user', instance.oper_user)

                if instance.flag == 1:      # 采购退货
                    instance.total_amount = instance.total_amount * (-1) if instance.total_amount > 0 else instance.total_amount
                    instance.amount = instance.amount * (-1) if instance.amount > 0 else instance.amount
                    if imprest:
                        instance.imp_amount = instance.imp_amount * (-1) if instance.imp_amount > 0 else instance.imp_amount

                instance.save()

                if old_imprest and imprest:  # 修改
                    update_account(old_account.id, -1 * old_imp_amount)
                    update_account(account.id, instance.imp_amount)
                else:
                    if imprest:
                        update_account(account.id, instance.imp_amount)
                    elif old_imprest:
                        update_account(old_account.id, -1 * old_imp_amount)

                for l in lists:
                    _id = l.pop("id", None)
                    l["batch_num"] = None if l["batch_num"] and l["batch_num"].strip() == "" else l["batch_num"]

                    if instance.flag == 1:  # 采购退货
                        if l["nums"] > 0:
                            l["nums"] *= -1
                        if l["total_amount"] > 0:
                            l["total_amount"] *= -1
                    if _id:
                        org_obj = WareHouseEntryDetail.objects.select_related('entry').filter(id=_id).first()
                        org_obj.old_house_id = old_house_id
                        WareHouseEntryDetail.objects.filter(id=_id).update(**l)
                        obj = WareHouseEntryDetail.objects.filter(id=_id).first()
                        increase_entry_stats(org_obj, -1)
                        increase_entry_stats(obj)
                    else:
                        org_obj = None
                        obj = WareHouseEntryDetail.objects.create(entry=instance, **l)
                        increase_entry_stats(obj)

                    self.update_goods_stock(instance.flag, obj, org_obj)
                    # 还回登记
                    if instance.flag == 3:
                        self.update_return_nums(obj)
            except Exception as e:
                print(e)
                transaction.savepoint_rollback(save_id)

            transaction.savepoint_commit(save_id)

        return instance

    def update_goods_stock(self, flag, obj, org_obj=None):
        if flag == 0 or flag == 2 or flag == 3:  # 采购入库/其他入库
            if org_obj is None:
                increase_goods_stock(obj)
            else:
                print(org_obj.old_house_id, org_obj.entry.house_id, obj.entry.house_id)
                decrease_goods_stock(org_obj)
                increase_goods_stock(obj)

        elif flag == 1:     # 采购退货
            stocks = GoodsStock.objects.filter(goods_id=obj.goods_id, house_id=obj.entry.house_id)
            nums = abs(obj.nums)
            old_nums = 0
            if org_obj:
                old_nums = abs(org_obj.nums)

            diff_nums = nums - old_nums
            for stock in stocks:
                if stock.quantity >= diff_nums:
                    stock.quantity = F('quantity') - diff_nums
                    stock.save(update_fields=['quantity'])
                    break
                else:
                    stock.quantity = 0
                    stock.price = 0
                    diff_nums -= stock.quantity
                    stock.save(update_fields=['quantity', 'price'])

            update_stock_price(obj.goods_id)

    def update_return_nums(self, obj):
        """"更新借出明细的归还数据"""
        results = WareHouseEntryDetail.objects.filter(sale_detail_id=obj.sale_detail_id).aggregate(total_nums=Sum('nums'))
        # print(results)
        if results and results.get("total_nums", None):
            return_nums = results.get("total_nums") or 0
            SaleOrderDetail.objects.filter(id=obj.sale_detail_id).update(return_nums=return_nums)


class PurchasePayOrderDetailSerializer(ModelSerializer):
    """
    采购付款单明细
    """
    id = serializers.IntegerField(required=False)
    pay_order_code = serializers.CharField(source='pay_order.code', read_only=True)
    pay_order_pay_time = serializers.CharField(source='pay_order.pay_time', read_only=True)
    pay_order_account_name = serializers.CharField(source='pay_order.account.name', read_only=True)
    entry_code = serializers.CharField(source='entry.code', read_only=True)
    entry_warehouse_time = serializers.CharField(source='entry.warehouse_time', read_only=True)
    entry_total_amount = serializers.CharField(source='entry.total_amount', read_only=True)
    entry_amount = serializers.CharField(source='entry.amount', read_only=True)
    flag_name = serializers.CharField(source='pay_order.get_flag_display', read_only=True)

    class Meta:
        model = PurchasePayOrderDetail
        exclude = ["update_time", "add_time"]


class PurchasePayOrderSerializer(ModelSerializer):
    """
    采购付款单
    """
    account_name = serializers.CharField(source='account.name', read_only=True)
    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
    user_name = serializers.CharField(source='user.real_name', read_only=True)
    oper_name = serializers.CharField(source='oper.real_name', read_only=True)
    flag_name = serializers.CharField(source='get_flag_display', read_only=True)
    details = PurchasePayOrderDetailSerializer(many=True)

    class Meta:
        model = PurchasePayOrder
        exclude = ["update_time", "add_time"]

    def create(self, validated_data):
        details = validated_data.pop('details')
        instance = None
        with transaction.atomic():
            save_id = transaction.savepoint()
            try:
                instance = self.Meta.model.objects.create(**validated_data)
                create_list = [PurchasePayOrderDetail(pay_order=instance, **d) for d in details]
                objs = PurchasePayOrderDetail.objects.bulk_create(create_list)
                # 减少账户金额
                self.update_account(instance, True)
                for obj in objs:
                    self.update_entry(obj)

            except Exception as e:
                print(e)
                transaction.savepoint_rollback(save_id)
            transaction.savepoint_commit(save_id)
        return instance

    def update(self, instance, validated_data):
        with transaction.atomic():
            save_id = transaction.savepoint()
            try:
                details = validated_data.pop('details')
                instance.code = validated_data.get('code', instance.code)
                old_account = instance.account
                instance.account = validated_data.get('account', instance.account)
                instance.pay_time = validated_data.get('pay_time', instance.pay_time)
                instance.supplier = validated_data.get('supplier', instance.supplier)
                instance.user = validated_data.get('user', instance.user)
                instance.payable = validated_data.get('payable', instance.payable)
                instance.pay_amount = validated_data.get('pay_amount', instance.pay_amount)
                instance.discount_amount = validated_data.get('discount_amount', instance.discount_amount)
                old_actual_amount = instance.actual_amount
                instance.actual_amount = validated_data.get('actual_amount', instance.actual_amount)
                instance.oper = validated_data.get('oper', instance.oper)
                instance.remark = validated_data.get('remark', instance.remark)
                instance.save()
                # 修改账户余额
                instance.old_account = old_account
                instance.old_actual_amount = old_actual_amount
                self.update_account(instance, False)

                for d in details:
                    detail_id = d.pop("id", None)
                    if detail_id:
                        org_obj = PurchasePayOrderDetail.objects.filter(id=detail_id).first()
                        PurchasePayOrderDetail.objects.filter(id=detail_id).update(**d)
                        obj = PurchasePayOrderDetail.objects.filter(id=detail_id).first()
                    else:
                        org_obj = None
                        obj = PurchasePayOrderDetail.objects.create(pay_order=instance, **d)

                    self.update_entry(obj, org_obj)
            except Exception as e:
                print(e)
                transaction.savepoint_rollback(save_id)
            transaction.savepoint_commit(save_id)

        return instance

    def update_entry(self, obj, org_obj=None):
        if org_obj is None:
            WareHouseEntry.objects.filter(id=obj.entry_id)\
                .update(
                        amount=F("amount") + obj.pay_amount,
                        status=Case(When(total_amount__lte=F("amount"), then=1), default=0),
                        order_status=1
                        )
        else:
            WareHouseEntry.objects.filter(id=obj.entry_id)\
                .update(
                        amount=F("amount") + obj.pay_amount - org_obj.pay_amount,
                        status=Case(When(total_amount__lte=F("amount"), then=1), default=0)
                        )

    def update_account(self, instance, flag):
        """更新账户金额"""
        if flag:    # 创建
            update_account(instance.account_id, instance.actual_amount)
        else:
            if instance.account == instance.old_account:
                update_account(instance.account_id, instance.actual_amount - instance.old_actual_amount)
            else:
                update_account(instance.account_id, instance.actual_amount)
                update_account(instance.old_account_id, -instance.old_actual_amount)


